// NOTICE
// 1. x, z(or ?) for unknown/high impedance valus : 4'b10x0, 8'h4x, 32'bz
// 2. assign y = a ? b : c;  //when a is 1'bx then y is 1'bx; if(a) will be interpreted as FALSE
// 3. == and === : a==b   //is a equal to b returns 1-bit true/false applies for 1 or 0, logic equality
						  //using X or Z operands returns always false
//				   a===b  //is a identical to b(includes 0,1,x,z)
// 4. concatenation :
//		a. {expression{,expression}}					//e.g, {mcnd, 6'b000000}
//		b. {expression{expression{,expression}}}		//e.g, {WIDTH{1'bz}}, assign dout = {4{2'b01}};  //i.e, 8'b0101_0101
// 5. given WIDTH, the max value is : (1<<WIDTH)-1    or (2**WIDTH)-1 , then  input [(1<<WIDTH)-1:0] din;
// 6. negetive num : reg [3:0] din;  din = -4'd5;  or -4'sd5;	//the value of din is 2'b1011 ??
// 7. memory : reg 	  [31:0]  DMA_CTRL[0:7];		//memory with depth 8 and width 32

//refer :
// The Verilog Hardware Description Language 5 Edition



// ==================== adder for arbitrary WIDTH ====================
// module adder(a, b, cin, sum, cout);
// parameter 		  WIDTH 	  = 4;
// input	  [WIDTH-1:0] a, b;
// input 			  cin;
// output	  [WIDTH-1:0] sum;
// output 			  cout;
// assign {cout, sum} = a + b + cin;
// endmodule

module adder #(
			   parameter WIDTH = 4
			   ) (
				  input [WIDTH-1:0]  a,
				  input [WIDTH-1:0]  b,
				  input 			 cin,
				  output [WIDTH-1:0] sum,
				  output 			 cout );
   assign {cout, sum} = a + b + cin;
endmodule
   
// ==================== encoder ====================
module encoder(
			   bin_out,			// 4 bit binary output
			   encoder_in,			// 16 bit input
			   en					// enable 
			   );
   output	  [3:0]   bin_out;
   input 	 [15:0]  encoder_in;
   input 			 en;
   
   reg [3:0] 		  bin_out;
   
   always@(en or encoder_in)
	 begin
		bin_out = 4'h0;
		if(en) begin
		   case(encoder_in)
			 // 16'h0001: bin_out = 4'h0;
			 16'h0002: bin_out = 4'h1;
			 16'h0004: bin_out = 4'h2;
			 16'h0008: bin_out = 4'h3;
			 16'h0010: bin_out = 4'h4;
			 16'h0020: bin_out = 4'h5;
			 16'h0040: bin_out = 4'h6;
			 16'h0080: bin_out = 4'h7;
			 16'h0100: bin_out = 4'h8;
			 16'h0200: bin_out = 4'h9;
			 16'h0400: bin_out = 4'ha;
			 16'h0800: bin_out = 4'hb;
			 16'h1000: bin_out = 4'hc;
			 16'h2000: bin_out = 4'hd;
			 16'h4000: bin_out = 4'he;
			 16'h8000: bin_out = 4'hf;
		   endcase
		end // if (en)
	 end
   
endmodule

// ==================== prior encoder ====================
//use if-else
module prior_encoder_use_ifelse(
								bin_out,									//4 bit binary output
								encoder_in,				//16 bit input
								en
								);
   output reg [3:0] bin_out;
   input [15:0] 	encoder_in;
   input 			en;

   always@(en or encoder_in)
	 begin
		if(!en)
		  bin_out = 4'h0;
		else if(encoder_in[15])
		  bin_out = 4'hf;
		else if(encoder_in[14])
		  bin_out = 4'he;
		else if(encoder_in[13])
		  bin_out = 4'hd;
		else if(encoder_in[12])
		  bin_out = 4'hc;
		else if(encoder_in[11])
		  bin_out = 4'hb;
		else if(encoder_in[10])
		  bin_out = 4'ha;
		else if(encoder_in[9])
		  bin_out = 4'h9;
		else if(encoder_in[8])
		  bin_out = 4'h8;
		else if(encoder_in[7])
		  bin_out = 4'h7;
		else if(encoder_in[6])
		  bin_out = 4'h6;
		else if(encoder_in[5])
		  bin_out = 4'h5;
		else if(encoder_in[4])
		  bin_out = 4'h4;
		else if(encoder_in[3])
		  bin_out = 4'h3;
		else if(encoder_in[2])
		  bin_out = 4'h2;
		else if(encoder_in[1])
		  bin_out = 4'h1;
		else
		  bin_out = 4'h0;
	 end // always@ (en or encoder_in)
endmodule // prior_encoder_use_ifelse

//use casex   ---- when the input(en=1) is 16'bxxxx_xxxx_xxxx_xxxx, then bin_out is 4'hf !
module prior_encoder(
					 bin_out,					//4 bit binary output
					 encoder_in,				//16 bit input
					 en
					 );
   output reg [3:0] bin_out;
   input [15:0] encoder_in;
   input 		en;

   always@(en or encoder_in)
	 begin
		bin_out = 4'h0;
		if(en) begin
		   casex(encoder_in)
			 16'b1xxx_xxxx_xxxx_xxxx : bin_out = 4'hf;	// 16'bxxxx_xxxx_xxxx_xxxx will match any branch ! (but only run the first)
			 16'b01xx_xxxx_xxxx_xxxx : bin_out = 4'he;
			 16'b001x_xxxx_xxxx_xxxx : bin_out = 4'hd;
			 16'b0001_xxxx_xxxx_xxxx : bin_out = 4'hc;
			 16'b0000_1xxx_xxxx_xxxx : bin_out = 4'hb;
			 16'b0000_01xx_xxxx_xxxx : bin_out = 4'ha;
			 16'b0000_001x_xxxx_xxxx : bin_out = 4'h9;
			 16'b0000_0001_xxxx_xxxx : bin_out = 4'h8;
			 16'b0000_0000_1xxx_xxxx : bin_out = 4'h7;
			 16'b0000_0000_01xx_xxxx : bin_out = 4'h6;
			 16'b0000_0000_001x_xxxx : bin_out = 4'h5;
			 16'b0000_0000_0001_xxxx : bin_out = 4'h4;
			 16'b0000_0000_0000_1xxx : bin_out = 4'h3;
			 16'b0000_0000_0000_01xx : bin_out = 4'h2;
			 16'b0000_0000_0000_001x : bin_out = 4'h1;
			 16'b0000_0000_0000_0001 : bin_out = 4'h0;		//this 0 is valid, others invalid
		   endcase // case (encoder_in)
		end
	 end // always@ (en or encoder_in)
endmodule // prior_encoder

		
// use assign    ---- when the input(en=1) is 16'bxxxx_xxxx_xxxx_xxxx, then bin_out is 4'hxx (ref NOTICE) !
module prior_encoder_using_assign(
					 bin_out,					//4 bit binary output
					 encoder_in,				//16 bit input
					 en
					 );
   output[3:0] bin_out;
   input [15:0] encoder_in;
   input 		en;

   assign bin_out = (!en) ? 0 : (
								 encoder_in[15] ? 4'hf :		//if encoder_in[15] is 1'bx then bin_out is 4'hx
								 encoder_in[14] ? 4'he :
								 encoder_in[13] ? 4'hd :
								 encoder_in[12] ? 4'hc :
								 encoder_in[11] ? 4'hb :
								 encoder_in[10] ? 4'ha :
								 encoder_in[9] ? 4'h9 :
								 encoder_in[8] ? 4'h8 :
								 encoder_in[7] ? 4'h7 :
								 encoder_in[6] ? 4'h6 :
								 encoder_in[5] ? 4'h5 :
								 encoder_in[4] ? 4'h4 :
								 encoder_in[3] ? 4'h3 :
								 encoder_in[2] ? 4'h2 :
								 encoder_in[1] ? 4'h1 : 4'h0 );
endmodule // prior_encoder_using_assign

module pri_encoder_nbit # (
						   parameter ENC_WIDTH = 3 //default 8-3 encoder   //when ENC_WIDTH is 5, error ??
						   ) (
							  input [(1<<ENC_WIDTH)-1:0] din,
							  output reg [ENC_WIDTH-1:0] bin_out,
							  output reg valid );
always@(*)
	 begin : encoder
		reg signed [(1<<ENC_WIDTH):0] 			i;	//one bit more than din
		bin_out = 0;
		valid = 0;
		for(i=(1<<ENC_WIDTH)-1;i>=0;i=i-1)
		  begin
			 if(din[i])
			   begin
				  bin_out = i[(1<<ENC_WIDTH)-1:0];
				  valid = 1;
				  disable encoder;
			   end
		  end
	 end // block: encoder
endmodule // pri_encoder_nbit



	 
// //module mux_pri (din, bin_out, valid);
// input 	  [3:0]   din;
// output reg [1:0]  bin_out;
// input 			  en;
// output reg 		  valid;

// always @*
// begin
//   if(en)
//   begin
// 	case (1)												//this is also valid !!!
// 		din[3] : begin bin_out = 2'b11; valid = 1'b1; end
// 		din[2] : begin bin_out = 2'b10; valid = 1'b1; end
// 		din[1] : begin bin_out = 2'b01; valid = 1'b1; end
// 		din[0] : begin bin_out = 2'b00; valid = 1'b1; end
// 		default : begin bin_out = 2'b00; valid = 1'b0; end
// 	endcase
//   end
//   else	//encode enable
//   begin
// 	bin_out = 2'b00; valid = 1'b0;
//   end
// end
// endmoudle



// ==================== decoder ====================
module decoder(
			   bin_in,				//4 bit binary in
			   dec_out,			//16 bit out
			   en
			   );
   input	  [3:0]   bin_in;
   output reg [15:0]  dec_out;
   input 			  en;
   
   always@(en or bin_in)
	 begin
		dec_out = 16'h0000;
		if(en)
		  begin
			 case(bin_in)
			   4'h0 : dec_out = 16'h0001;
			   4'h1 : dec_out = 16'h0002;
			   4'h2 : dec_out = 16'h0004;
			   4'h3 : dec_out = 16'h0008;
			   4'h4 : dec_out = 16'h0010;
			   4'h5 : dec_out = 16'h0020;
			   4'h6 : dec_out = 16'h0040;
			   4'h7 : dec_out = 16'h0080;
			   4'h8 : dec_out = 16'h0100;
			   4'h9 : dec_out = 16'h0200;
			   4'ha : dec_out = 16'h0400;
			   4'hb : dec_out = 16'h0800;
			   4'hc : dec_out = 16'h1000;
			   4'hd : dec_out = 16'h2000;
			   4'he : dec_out = 16'h4000;
			   4'hf : dec_out = 16'h8000;
			 endcase // case (bin_in)
		  end // if (en)
	 end // always@ (en or bin_in)
endmodule

module decoder_using_assign(
							bin_in,				//4 bit binary in
							dec_out,			//16 bit out
							en
							);
   input [3:0] bin_in;
   output [15:0] dec_out;
   input 		 en;

   // assign dec_out = 1 << bin_in;
   assign dec_out = en ? (1 << bin_in) : 16'b0;
   
endmodule // decoder_using_assign

//decoder for arbitrary-width input
module decoder_nbit #(
					  parameter WIDTH = 4
					  ) (
						 input [WIDTH-1:0] 		 bin_in,
						 output [(1<<WIDTH)-1:0] dec_out,		//or [2**WIDTH-1:0]
						 input 					 en );
   assign dec_out = en ? (1 << bin_in) : 0;
endmodule // decoder_nbit

// ==================== multiply ====================
// refer : The Verilog Hardware Description Language Fifth Edition, 3.5 Functions and Tasks
module multiply #(
				  parameter WIDTH = 4
				  ) (
					 input [WIDTH-1:0] 	  opa, opb,
					 output [WIDTH*2-1:0] prod );
					 //output reg [WIDTH*2-1:0] prod );			//for task
   
   assign prod = multiply_func(opa, opb);
   
   // always@(opa or opb)
   // 	 multiply_task(opa, opb, prod);

   task multiply_task;
	  input 	[WIDTH-1:0] opa, opb;
	  output [WIDTH*2-1:0] 	result;
	  
	  begin : serialMult
		 reg    [WIDTH*2-1:0] mcnd;			//local variable
		 // reg [WIDTH*2-1:0] 	  mpy;		//only need shift right
		 reg [WIDTH-1:0] 	  mpy;
		 
		 mcnd = opa;
		 mpy = opb;
		 result = 0;
		 repeat(WIDTH)
		   begin
			  if(mpy[0])
				result = result + mcnd;
			  mcnd = mcnd << 1;
			  mpy = mpy >> 1;
		   end
	  end // block: serialMult
   endtask // multiply_task

   function [WIDTH*2-1:0] multiply_func;
	  input [WIDTH-1:0] opa, opb;

	  begin : serialMult
		 reg [WIDTH*2-1:0] mcnd;
		 reg [WIDTH-1:0]   mpy;

		 mcnd = opa;
		 mpy = opb;
		 multiply_func = 0;
		 repeat(WIDTH)
		   begin
			  if(mpy[0])
				multiply_func = multiply_func + mcnd;
			  mcnd = mcnd << 1;
			  mpy = mpy >> 1;
		   end
	  end // block: serialMult
   endfunction
   
   
   
   
endmodule // multiply

// ANSI C style
// task multiply_task(
// 				   output [7:0] dout,
// 				   input [7:0] 	din);
   


// ===== some software implement for multiply =====
// unsigned int multipy(unsigned short a, unsigned short b)
// {
//   unsigned int R = 0;
//   unsigned int mcnd = a;        //extend to 32-bit
//   unsigned int mpy = b;
//   int i;
//   //the first method (left shift)
//   for(i=0;i<16;i++)
//     {
//       if(mpy&1)
//         {
//           R += mcnd;
//         }
//       mcnd <<= 1;
//       mpy >>= 1;
//     }
//   return R;

//   //the second method (right shift)
//   //NOTICE : R may overflow (R should be 33-bit wide, since R first added then shift right), e.g, 64854*334
//   for(i=0;i<16;i++)
//     {
//       if(mpy&1)
//         {
//           R += mcnd<<16;
//         }
//       R >>= 1;
//       mpy >>= 1;
//       printf("R = %x\n", R);
//     }
//   return R;

//   //the basic method
//   for(i=0;i<16;i++)
//     {
//       if((b>>i)&1)
//         R += a<<i;
//     }
//   return R;
// }


// ==================== FSM ====================
// the general FSM


// a single loop without any conditional next states
module synImplicit
  (input  [7:0] dataIn, c1, c2,
   input 			clock,
   output reg [7:0] dataOut);

   reg [7:0] 		temp;

   always begin
	  @(posedge clock)			//the first clock posedge(implicit state) //a clock event
		temp = dataIn + c1;		//the statement between the two clock events constitute a state (include the clock event)

	  @(posedge clock)
		temp = temp & c2;

	  @(posedge clock)			//the third
		dataOut = temp - c1;
   end
endmodule // synImplicit

// pipeline (another implicit FSM)
module synPipe
  (input  [7:0] dataIn, c1, c2,
   input 			clock,
   output reg [7:0] dataOut);
   
   reg [7:0] 		stageOne;
   
   reg [7:0] 		stageTwo;
   
   always@(posedge clock)
	 stageOne <= dataIn + c1;
   
   always@(posedge clock)
	 stageTwo <= stageOne & c2;
   
   always@(posedge clock)
	 dataOut <= stageTwo + stageOne;
endmodule // synPipe




// ==================== testbench ====================
module test();

   // --------------- for encoder & decoder ---------------
   reg		  [15:0]  encoder_in;
   wire [3:0] enc_out0, enc_out1, enc_out2, enc_out3;
   reg 		  en;

   encoder encoder0 (.bin_out(enc_out0), .encoder_in(encoder_in), .en(en));
   prior_encoder prior_encoder0 (.bin_out(enc_out1), .encoder_in(encoder_in), .en(en));
   prior_encoder_use_ifelse prior_encoder_ifelse0 (.bin_out(enc_out2), .encoder_in(encoder_in), .en(en));
   prior_encoder_using_assign prior_encoder_assign0 (.bin_out(enc_out3), .encoder_in(encoder_in), .en(en));

   wire [15:0] dec_out0, dec_out1, dec_out2;
   decoder decoder0 (.bin_in(enc_out2), .dec_out(dec_out0), .en(en));
   decoder_using_assign decoder_as0 (.bin_in(enc_out2), .dec_out(dec_out1), .en(en));

   //decoder_nbit #(.WIDTH(4)) decoder_nbit (.bin_in(enc_out2), .dec_out(dec_out2), .en(en));
   wire [7:0]  dec38;
   //decoder_nbit #(.WIDTH(3)) decoder_nbit (.bin_in(enc_out2[2:0]), .dec_out(dec_out2[7:0]), .en(en));	//3-8 decoder
   decoder_nbit #(.WIDTH(3)) decoder_nbit (.bin_in(enc_out2[2:0]), .dec_out(dec38), .en(en));	//3-8 decoder

   //test the multiply
   reg [3:0]   multiply_opa, multiply_opb;
   wire [7:0]  multiply_prod;
   multiply #(.WIDTH(4)) multiply_0 (multiply_opa, multiply_opb, multiply_prod);
   
   
   integer 	  i;
   initial begin
	  en = 0;
	  #10 en = 1;
	  #10 encoder_in = 16'h0000;		//this value not valid
	  
	  for(i=0;i<16;i=i+1)
		#10 encoder_in = 16'h1 << i;

	  //for(i=0;i<1000;i=i+1)
	  // forever #10 encoder_in = $random%32'h10000;
	  forever #10 begin
		 encoder_in = $random%32'h10000;
		 //encoder_in = $random%32'd10;
		 $display("encode(%4x) => %x", encoder_in, enc_out1);
	  end

   end // initial begin

   //for multiply
   initial
	 begin
		forever #10
		  begin
			 multiply_opa = $random%16;
			 multiply_opb = $random%16;

			 #5 if(multiply_prod != multiply_opa * multiply_opb)
			   begin
				  $display("multiply : not equal : %d * %d -> %d", multiply_opa, multiply_opb, multiply_prod);
				  $stop;
			   end
		  end
	 end
   

   
   // --------------- for adder ---------------
   reg [7:0] a, b;
   reg 		 cin;
   wire [7:0] sum;
   wire 	  cout;
 
   adder #( 
			.WIDTH(8)
			) adder_8
	 (
	  .a(a),
	  .b(b),
	  .cin(cin),
	  .sum(sum),
	  .cout(cout) );

   integer 	  adder_i;
   initial
	 begin
		cin = 0;
		#10 a = 8'd0; b = 8'd0;
		#10 a = 8'd1; b = 8'd2;
		#10 a = 8'd120; b = 8'd45;
		forever #10
		  begin
			 a = $random%256;
			 b = $random%256;
			 cin = $random%2;
		  end
	 end // initial begin

   initial #1000 $stop;

endmodule // test


   
/* 
 Scope & Hierarchical Names
Local variables : 
identifiers to be defined within four entities: modules, tasks, functions, and named blocks. 
 */
// module test();
//    reg 			  clk;			//test.clk

//    wire [7:0] 	  cnt;			//test.cnt
//    //counter c1(clk, cnt);		//test.c1  //test.c1.cnt
   
//    reg [7:0] 	  dout;
//    always@(posedge clk)
// 	 begin : test_block					//named block, test.test_block
// 		reg 		[7:0] 	tmp_in;		//local variable, but can be set by "test_block.tmp_in"
// 		// dout <= dout + 1;
// 		dout <= tmp_in;
// 	 end

//    initial 
// 	 begin : initial_block
// 		integer 			i;			//local var
// 		for(i=0;i<3;i=i+1)
// 		  $display("initial ...");
// 		$display("dout = %x", dout);
// 		# 10 test_block.tmp_in = 8'h34;		//could set the local variable
// 		# 10 $display("dout = %x", dout);

// 		//c1.increase_block.tmp = 8'h12;
		
// 	 end

// initial begin 
//   clk = 0;
//   forever #5 clk = ~clk;
// end

// endmodule



// ==================== others  ====================
//latch
module latch(d, en, rst, q);
   input d, en, rst;
   output reg q;
   always@(d or en or rst) 
	 begin
		if(rst)
		  q <= 0;
		else if(en)
		  q <= d;
	 end
endmodule // latch


